#pragma once

#include <memory>
#include <string>
#include "common/json.h"


namespace evm { namespace messaging {

using namespace std;

struct Target;

using TargetPtr = shared_ptr<Target>;
using Targets = vector<TargetPtr>;

/* Identifies the destination of messages.
 * A Target encapsulates all the information to identify where a message
 * should be sent or what messages a server is listening for.
 *
 * Different subsets of the information encapsulated in a Target object is
 * relevant to various aspects of the API:
 * a) Construct a rpc server's target: topic is required, server is optional
 * b) Construct a rpc endpoint's target: namespace_ is optional
 * c) Construct a rpc client's target: topic is required, namespace_ and fanout are optional
 * d) Construct a notification listener's or notifier's target: topic is required
 */
struct Target {
    /* Construct a rpc server's target: topic is required, server is optional. */
    static TargetPtr server_target(string topic, string server = "") {
        return make_shared<Target>(topic, string(), server, false);
    }

    /* Construct a rpc endpoint's target: namespace_ is optional. */
    static TargetPtr endpoint_target(string namespace_ = "") {
        return make_shared<Target>(string(), namespace_, string(), false);
    }

    static TargetPtr client_target(string topic, string namespace_ = "", string server = "", bool fanout = false) {
        return make_shared<Target>(topic, namespace_, server, fanout);
    }

    static TargetPtr notification_target(string topic) {
        return make_shared<Target>(topic, string(), string(), false);
    }

    Target() {}

    /* Construct target from json parameter, with missing fields default constructed. */
    Target(const Json& target)
    : topic(target.count("topic") ? target["topic"].get<string>() : string()),
      namespace_(target.count("namespace") ? target["namespace"].get<string>() : string()),
      server(target.count("server") ? target["server"].get<string>() : string()),
      fanout(target.count("fanout") ? target["fanout"].get<bool>() : false) {}

    Target(string topic, string namespace_, string server, bool fanout)
        : topic(topic), namespace_(namespace_), server(server), fanout(fanout) {}

    /* Copy a new target with some fields overridden. */
    TargetPtr operator()(const Json& vars) {
        return make_shared<Target>(
            vars.count("topic") ? vars["topic"].get<string>() : topic,
            vars.count("namespace") ? vars["namespace"].get<string>() : namespace_,
            vars.count("server") ? vars["server"].get<string>() : server,
            vars.count("fanout") ? vars["fanout"].get<bool>() : fanout);
    }

    /* Return a JSON representation. */
    /*Json json() {
        return {
            {"topic", topic},
            {"namespace", namespace_},
            {"server", server},
            {"fanout", fanout}
        };
    }*/

    string to_str() {
        return Json({
                    {"topic", topic},
                    {"namespace", namespace_},
                    {"server", server},
                    {"fanout", fanout}
                    }
        ).dump();
    }

    /* A name which identifies the set of interfaces exposed by a
     * server. Multiple servers may listen on a topic and messages will be
     * dispatched to one of the servers in a round-robin fashion.
     */
    string topic;

    /* Identifies a particular interface (i.e. set of methods)
     * exposed by a server. The default interface has no namespace identifier
     * and is referred to as the null namespace.
    */
    string namespace_;

    /* Clients can request that a message be directed to a specific
     * server, rather than just one of a pool of servers listening on the topic.
     */
    string server;

    /* Clients may request that a message be directed to all
     * servers listening on a topic by setting fanout to true, rather than
     * just one of them. Defaults to false.
     */
    bool fanout{};
};

}}
